package com.even.okhttp.helper.library

import android.os.Handler
import android.os.Looper
import cn.dazhou.railway.monitor.network.ApiRequestCallback
import cn.dazhou.railway.monitor.network.TypeToken
import com.evendai.loglibrary.Timber
import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.logging.HttpLoggingInterceptor
import java.io.File
import java.io.IOException
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit

object OkHttpHelper {

    private val uiHandler = Handler(Looper.getMainLooper())
    private val okHttpLogger = HttpLoggingInterceptor.Logger { message -> Timber.i(message) }

    private val okHttpClient = OkHttpClientBuilder.createOkHttpClientBuilder()
//        .addInterceptor(OkHttpInterceptor())
//        .addInterceptor(HttpLoggingInterceptor(myLogger).apply { level = HttpLoggingInterceptor.Level.BODY })
//        .addInterceptor(TestInterceptor())
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .writeTimeout(60, TimeUnit.SECONDS)
        .build()

    /** 网络请求方法 */
    private fun <T> request(request: Request, callback: ApiRequestCallback<T>) {
        okHttpClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                Timber.fw(e,"OkHttp请求出现异常, 请求方式：${request.method}, url = ${request.url}")
                val failCause = if (e is SocketTimeoutException || e.message == "timeout") "请求超时，请稍后再试。" else "请求出现异常：${e.message}"
                uiHandler.post { callback.onApiRequestFail(failCause) }
            }

            override fun onResponse(call: Call, response: Response) {
                response.use { processResponse(response, callback) }
            }
        })
    }

    private fun <T> processResponse(response: Response, callback: ApiRequestCallback<T>): T? {
        Timber.i("result = ${response.body?.string()}")
        return null
    }

    /** 请求构造器，使用三步曲：1、指定请求方式和url，2、添加请求参数，3、执行go函数开始请求 */
    class Builder {
        /** 请求构建器 */
        private val requestBuilder = Request.Builder()
        /** 请求方式，GET或POST */
        private var method: String? = null
        /** 请求地址 */
        private lateinit var url: HttpUrl
        /** JSON请求时用到的JavaBean */
        private var javaBean: Any? = null
        /** 上传文件时用到的请求体构建器 */
        private var multipartBodyBuilder: MultipartBody.Builder? = null
        private var params = mutableMapOf<String, String>()

        /**
         * get请求
         * @throws IllegalArgumentException 如果url不是一个格式正确的HTTP或HTTPS的URL。
         */
        fun get(url: String) = apply {
            this.url = url.toHttpUrl()
            method = "GET"
        }

        /**
         * post请求
         * @throws IllegalArgumentException 如果url不是一个格式正确的HTTP或HTTPS的URL。
         */
        fun post(url: String) = apply {
            this.url = url.toHttpUrl()
            method = "POST"
        }

        /**
         * 提交Json的请求，把javaBean传进来会自动转换为json传给服务器
         * @throws IllegalArgumentException 如果url不是一个格式正确的HTTP或HTTPS的URL。
         */
        @Suppress("unused")
        fun json(url: String, javaBean: Any) = apply {
            post(url)
            this.javaBean = javaBean
        }

        /**
         * 上传文件
         * @throws IllegalArgumentException 如果url不是一个格式正确的HTTP或HTTPS的URL。
         */
        fun uploadFile(url: String) = apply {
            post(url)
            multipartBodyBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
        }

        /** 添加上传文件的参数（普通的String参数） */
        fun addUploadFileParam(key: String, value: String) = apply {
            multipartBodyBuilder?.apply { addFormDataPart(key, value)}
        }

        /**
         * 添加上传文件的参数（指定要上传的文件）
         * @param key
         * @param value 指定要上传的文件
         * @param mime 指定要上传的文件类型
         * @param fileName 指定上传文件的名称，可以不传，默认为文件名
         */
        fun addUploadFileParam(key: String, value: File, mime: String, fileName: String = value.name) = apply {
            multipartBodyBuilder?.apply { addFormDataPart(key, fileName, value.asRequestBody(mime.toMediaTypeOrNull())) }
        }

        /** 添加请求头 */
        fun addHeader(name: String, value: String) = apply {
            requestBuilder.addHeader(name, value)
        }

        /** 添加请求参数 */
        fun addParam(key: String, value: String) = apply {
            params[key] = value
        }

        /**
         * 执行请求（异步方式）
         * @param callback 此回调参数用于接收请求结果，回调在主线程上运行
         */
        fun <T> go(callback: ApiRequestCallback<T>) {
            val request = createRequest()
            request(request, callback)
        }

        /**
         * 执行请求（同步方式）
         * @param typeToken 用于指定json要解析成的JavaBean类型，如要解析成Person类型，则传：object: TypeToken<Person>() {}
         */
        fun <T> syncGo(typeToken: TypeToken<T>): T? {
            val request = createRequest()
            return try {
                okHttpClient.newCall(request).execute().use { processResponse(it, typeToken) }
            } catch (e: Exception) {
                Timber.e(e, "同步请求出现异常")
                null
            }
        }

        private fun createRequest(): Request {
            if (method == "GET") {
                // GET请求
                val newUrl = url.newBuilder()
                    .apply { params.forEach { (key, value) -> addQueryParameter(key, value) } }
                    .build()
                requestBuilder.url(newUrl).get()
            } else {
                // POST请求
                val requestBody = when {
                    // JSON请求
                    javaBean != null -> JsonUtil.toJson(javaBean!!)!!.toRequestBody("application/json; charset=utf-8".toMediaType())
                    // 上传文件请求
                    multipartBodyBuilder != null -> multipartBodyBuilder!!.build()
                    // 普通POST请求
                    else -> FormBody.Builder().apply { params.forEach { (key, value) -> add(key, value) } }.build()
                }
                requestBuilder.url(url).post(requestBody)
            }

            return requestBuilder.build()
        }

    }

}